﻿namespace Sai.Core
{
    using Sai.Core.Reporting;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Xml.Serialization;

    [Serializable]
	public class TypeDifference : ITypeDifference
	{
        public TypeDifference()
        {
            this.ReportOnAdditions 
                = this.ReportOnChanges 
                = this.ReportOnObsolete 
                = this.ReportOnRemovals = true;
        }

		private Identifier ident = new Identifier();

		private Dictionary<TypeChangeReason, List<IDiscoveredTypeChange>> changes = new Dictionary<TypeChangeReason, List<IDiscoveredTypeChange>>();

		[XmlAttribute("SummaryID")]
		public string SummaryIdentifier { get; set; }

		[XmlAttribute("SummaryName")]
		public string SummaryName { get; set; }

		[XmlAttribute("AssemblyID")]
		public string AssemblyIdentifier { get; set; }

		[XmlAttribute("AssemblyName")]
		public string AssemblyName { get; set; }

		[XmlAttribute("ID")]
		public string Identifier
		{
			get
			{
				return this.ident.Value;
			}

			set
			{
				this.ident = new Identifier(value);
			}
		}
				
        [XmlAttribute("NamespaceName")]
        public string NamespaceName
        {
            get;
            set;
        }

        [XmlAttribute("Name")]
        public string Name
        {
            get;
            set;
        }

        [XmlIgnore]
        public string FullName
        {
            get
            {
                if (string.IsNullOrEmpty(this.NamespaceName))
                    return this.Name;

                return string.Format("{0}.{1}", this.NamespaceName, this.Name);
            }
        }

			
		[XmlAttribute("From")]
		public string FromVersion
		{
			get;
			set;
		}

		[XmlAttribute("To")]
		public string ToVersion
		{
			get;
			set;
		}

		[XmlAttribute("ProductName")]
		public string ProductName
		{
			get;
			set;
		}

		[XmlAttribute("ProductLink")]
		public string ProductLink
		{
			get;
			set;
		}

		[XmlAttribute("ProductVersion")]
		public string ProductVersion
		{
			get;
			set;
		}

        /// <summary>
        /// Definition of type in source code.
        /// </summary>
        public SourceCode SourceDefinition
        {
            get;
            set;
        }

        [XmlIgnore]
        public bool ReportOnRemovals { get; set; }

        [XmlIgnore]
        public bool ReportOnAdditions { get; set; }

        [XmlIgnore]
        public bool ReportOnChanges { get; set; }

        [XmlIgnore]
        public bool ReportOnObsolete { get; set; }

		[XmlIgnore]
		public bool HasChanged { get; private set; }
       
		public void SaveTo(string fileName)
		{
			using (var writer = new StreamWriter(fileName))
			{
				this.SaveTo(writer);
			}
		}

		public void SaveTo(TextWriter writer)
		{
            ObjectFlattener<TypeDifference>.Flatten(this, writer);
		}

		public string ToXml()
		{
			using (var writer = new StringWriter())
			{
				this.SaveTo(writer);
				return writer.ToString();
			}
		}

        private IList<IDiscoveredTypeChange> AllChangesFor(TypeChangeReason reason)
        {
            if (!this.changes.ContainsKey(reason))
                return new List<IDiscoveredTypeChange>();

            return this.changes[reason];
        }

        public void IdentifiedChange(TypeChangeReason reason, ISourceCodeProvider sourceCode)
        {
            this.IdentifiedChange(reason, sourceCode, string.Empty);
        }

        public void IdentifiedChange(TypeChangeReason reason, ISourceCodeProvider sourceCode, string message)
        {
            this.IdentifiedChange(
                new UnaryTypeChange(
                    reason, 
                    sourceCode,
                    message));
        }

        public void IdentifiedChange(TypeChangeReason reason, ISourceCodeProvider sourceCode, IMessageProvider message)
        {
            this.IdentifiedChange(reason, sourceCode, message.Message);
        }

        public void IdentifiedChange(TypeChangeReason reason, ISourceCodeProvider was, ISourceCodeProvider isNow, string message)
        {
            this.IdentifiedChange(
                new BinaryTypeChange(
                    reason,
                    was,
                    isNow,
                    message));
        }

        public void IdentifiedChange(TypeChangeReason reason, ISourceCodeProvider was, ISourceCodeProvider isNow, IMessageProvider message)
        {
            this.IdentifiedChange(reason, was, isNow, message.Message);
        }

        public void IdentifiedChange(IDiscoveredTypeChange change)
        {
            bool accept = (change.Reason.IsAddition() && this.ReportOnAdditions)
                || (change.Reason.IsRemoval() && this.ReportOnRemovals)
                || (change.Reason.IsObsolete() && this.ReportOnObsolete)
                || (change.Reason.IsChange() && this.ReportOnChanges);

            if (!accept)
                return;

            if (!this.changes.ContainsKey(change.Reason))
            {
                var list = new List<IDiscoveredTypeChange>();
                list.Add(change);

                this.changes.Add(change.Reason, list);
            }
            else
            {
                this.changes[change.Reason].Add(change);
            }

            this.HasChanged = true;
        }


        [XmlArray("TypeTaxonomy", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> TypeTaxonomy
        {
            get 
            {
                List<UnaryTypeChange> allChanges = new List<UnaryTypeChange>();

                allChanges.AddRange(this.AllChangesFor(TypeChangeReason.TypeTaxonomy).Cast<UnaryTypeChange>());
                allChanges.AddRange(this.AllChangesFor(TypeChangeReason.TypeSealed).Cast<UnaryTypeChange>());
                allChanges.AddRange(this.AllChangesFor(TypeChangeReason.TypeInterfaceRemoved).Cast<UnaryTypeChange>());
                allChanges.AddRange(this.AllChangesFor(TypeChangeReason.TypeInterfaceAdded).Cast<UnaryTypeChange>());
                allChanges.AddRange(this.AllChangesFor(TypeChangeReason.TypeAbstract).Cast<UnaryTypeChange>());

                
                return allChanges.Any() ? allChanges : null;
            }
        }

        [XmlArray("InheritanceChanged", IsNullable = false)]
        [XmlArrayItem("BinaryTypeChange", typeof(BinaryTypeChange))]
        public List<BinaryTypeChange> InheritanceChanged
        {
            get
            {
                List<BinaryTypeChange> list = new List<BinaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.TypeInheritance)
                        .Cast<BinaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("ConstantsObsolete", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> ConstantsObsolete
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.ConstantObsolete)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;

            }
        }

        [XmlArray("ConstantsRemoved", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> ConstantsRemoved
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.ConstantRemoved) 
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("ConstantsChanged", IsNullable = false)]
        [XmlArrayItem("BinaryTypeChange", typeof(BinaryTypeChange))]
        public List<BinaryTypeChange> ConstantsChanged
        {
            get 
            {
                List<BinaryTypeChange> list = new List<BinaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.ConstantChanged)
                        .Cast<BinaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("ConstantsAdded", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> ConstantsAdded
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.ConstantAdded)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("FieldsObsolete", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> FieldsObsolete
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.FieldObsolete)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("FieldsRemoved", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> FieldsRemoved
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.FieldRemoved)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("FieldsChanged", IsNullable = false)]
        [XmlArrayItem("BinaryTypeChange", typeof(BinaryTypeChange))]
        public List<BinaryTypeChange> FieldsChanged
        {
            get 
            {
                List<BinaryTypeChange> list = new List<BinaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.FieldChanged)
                        .Cast<BinaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("FieldsAdded", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> FieldsAdded
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.FieldAdded)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("ConstructorsObsolete", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> ConstructorsObsolete
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.ConstructorObsolete)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("ConstructorsRemoved", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> ConstructorsRemoved
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.ConstructorRemoved)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("FinalizersRemoved", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> FinalizersRemoved
        {
            get
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.FinalizerRemoved)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("ConstructorsChanged", IsNullable = false)]
        [XmlArrayItem("BinaryTypeChange", typeof(BinaryTypeChange))]
        public List<BinaryTypeChange> ConstructorsChanged
        {
            get 
            {
                List<BinaryTypeChange> list = new List<BinaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.ConstructorChanged)
                        .Cast<BinaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("ConstructorsAdded", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> ConstructorsAdded
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.ConstructorAdded)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("FinalizersAdded", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> FinalizersAdded
        {
            get
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.FinalizerAdded)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("EventsObsolete", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> EventsObsolete
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.EventObsolete)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("EventsRemoved", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> EventsRemoved
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.EventRemoved)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("EventsChanged", IsNullable = false)]
        [XmlArrayItem("BinaryTypeChange", typeof(BinaryTypeChange))]
        public List<BinaryTypeChange> EventsChanged
        {
            get 
            {
                List<BinaryTypeChange> list = new List<BinaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.EventChanged)
                        .Cast<BinaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("EventsAdded", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> EventsAdded
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.EventAdded)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("PropertiesObsolete", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> PropertiesObsolete
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.PropertyObsolete)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("PropertiesRemoved", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> PropertiesRemoved
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.PropertyRemoved)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("PropertiesChanged", IsNullable = false)]
        [XmlArrayItem("BinaryTypeChange", typeof(BinaryTypeChange))]
        public List<BinaryTypeChange> PropertiesChanged
        {
            get 
            {
                List<BinaryTypeChange> list = new List<BinaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.PropertyChanged)
                        .Cast<BinaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("PropertiesAdded", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> PropertiesAdded
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.PropertyAdded)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("IndexersObsolete", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> IndexersObsolete
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.IndexerObsolete)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("IndexersRemoved", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> IndexersRemoved
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.IndexerRemoved)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("IndexersChanged", IsNullable = false)]
        [XmlArrayItem("BinaryTypeChange", typeof(BinaryTypeChange))]
        public List<BinaryTypeChange> IndexersChanged
        {
            get 
            {
                List<BinaryTypeChange> list = new List<BinaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.IndexerChanged)
                        .Cast<BinaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("IndexersAdded", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> IndexersAdded
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.IndexerAdded)
                        .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("MethodsObsolete", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> MethodsObsolete
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.MethodObsolete)
                    .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("MethodsRemoved", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> MethodsRemoved
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.MethodRemoved)
                        .Cast<UnaryTypeChange>()
                        );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("MethodsChanged", IsNullable = false)]
        [XmlArrayItem("BinaryTypeChange", typeof(BinaryTypeChange))]
        public List<BinaryTypeChange> MethodsChanged
        {
            get 
            {
                List<BinaryTypeChange> list = new List<BinaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.MethodChanged)
                    .Cast<BinaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("MethodsAdded", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> MethodsAdded
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.MethodAdded)
                        .Cast<UnaryTypeChange>()
                        );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("ValuesRemoved", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> ValuesRemoved
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.ValueRemoved)
                        .Cast<UnaryTypeChange>()
                        );

                return list.Any() ? list : null;
            }
        }

        [XmlArray("ValuesAdded", IsNullable = false)]
        [XmlArrayItem("UnaryTypeChange", typeof(UnaryTypeChange))]
        public List<UnaryTypeChange> ValuesAdded
        {
            get 
            {
                List<UnaryTypeChange> list = new List<UnaryTypeChange>(
                    this.AllChangesFor(TypeChangeReason.ValueAdded)
                    .Cast<UnaryTypeChange>()
                    );

                return list.Any() ? list : null;
            }
        }
    }
}
